local ROT = require 'rot'
local RNG = ROT.RNG

return {

    delay = function (mob, game, scene)
        if mob.delay == 0 then return mob.stamina <= 0 end

        mob.delay = math.max(mob.delay - 1, 0)

        return true
    end,

    regen = function (mob, game, scene)
        if not (mob.healthRegen and mob.health and mob.maxHealth) then return false end

        mob.health = math.min(mob.health + mob.healthRegen, mob.maxHealth)

        return true
    end,

    rest = function (mob, game, scene)
        if not (mob.staminaRegen and mob.stamina and mob.maxStamina) then return false end

        mob.stamina = math.min(mob.stamina + mob.staminaRegen, mob.maxStamina)

        return true
    end,

    death = function (mob, game, scene)
        if not mob.health then return false end

        if mob.health <= 0 then
            if mob.effects then
                for i = 1, #mob.effects do mob.effects[i].revoke(mob, game) end
            end
            scene.mob:setCell(mob.x, mob.y, nil)
            mob.dead = true
            game:log('The ', mob.name, ' dies.')
            if mob.corpse then
                scene:putItem(mob.x, mob.y, mob.corpse, {
                        name = mob.name .. ' ' .. mob.corpse,
                })
            elseif not mob.corpseless then
                scene:putItem(mob.x, mob.y, 'corpse', {
                    name = mob.name .. ' corpse',
                    color = mob.color,
                })
            end
            for i = 1, #mob.stuff do
                scene:putItem(mob.x, mob.y, mob.stuff[i])
            end
        end

        return true
    end,

    wander = function (mob, game, scene)
        if not mob.health then return false end

        local newX, newY = mob.x + RNG:random(-1,1), mob.y + RNG:random(-1,1)
        if scene:canMove(mob, newX, newY) then
            scene:move(mob, newX, newY)
        end

        return true
    end,

    flee = function (mob, game, scene)
        local tx, ty
        if mob.target then
            tx, ty = mob.target.x, mob.target.y
        else
            tx, ty = mob.lastTargetX, mob.lastTargetY
        end

        if not (tx and ty) then return false end

        if mob.health > mob.maxHealth * (1 - (mob.morale or 0.5))
        and mob.stamina > mob.maxStamina * (1 - (mob.morale or 0.5)) * 0.25 then
            if mob.fleeing and mob.visible then
                game:log(mob.definite, ' looks braver.')
            end
            mob.fleeing = false
            return false
        end

        local vx = tx < mob.x and -1
            or tx > mob.x and 1
            or 0
        local vy = ty < mob.y and -1
            or ty > mob.y and 1
            or 0

        local newX = mob.x - vx
        local newY = mob.y - vy

        if scene:canMove(mob, newX, newY) then
            scene:move(mob, newX, newY)
        elseif scene:canMove(mob, mob.x, newY) then
            scene:move(mob, mob.x, newY)
        elseif scene:canMove(mob, newX, mob.y) then
            scene:move(mob, newX, mob.y)
        else
            local done
            for i = 1, 4 do
                newX, newY = mob.x + RNG:random(-1,1), mob.y + RNG:random(-1,1)
                if scene:canMove(mob, newX, newY) then
                    scene:move(mob, newX, newY)
                    done = true
                    break
                end
            end
            if done then
            elseif mob.target and scene:canAttack(mob, mob.target.x, mob.target.y) then
                game:log('Cornered, ', mob.definite, ' attacks!')
                scene:attack(mob, mob.target)
            elseif mob.visible then
                game:log(mob.definite, ' panics!')
            end
        end

        if (not mob.fleeing) and mob.visible then
            game:log(mob.definite, ' turns to flee!')
        end

        mob.fleeing = true
        return true
    end,

    attack = function (mob, game, scene)
        local tx, ty = mob.lastTargetX, mob.lastTargetY

        if not (tx and ty) then return false end

        local newX = tx < mob.x and mob.x - 1
            or tx > mob.x and mob.x + 1
            or mob.x
        local newY = ty < mob.y and mob.y - 1
            or ty > mob.y and mob.y + 1
            or mob.y
        if mob.target and scene:canAttack(mob, mob.target.x, mob.target.y) then
            scene:attack(mob, mob.target)
        elseif scene:canMove(mob, newX, newY) then
            scene:move(mob, newX, newY)
        elseif scene:canMove(mob, mob.x, newY) then
            scene:move(mob, mob.x, newY)
        elseif scene:canMove(mob, newX, mob.y) then
            scene:move(mob, newX, mob.y)
        else
            newX, newY = mob.x + RNG:random(-1,1), mob.y + RNG:random(-1,1)
            if scene:canMove(mob, newX, newY) then
                scene:move(mob, newX, newY)
            end
        end

        if mob.x == tx and mob.y == ty then
            mob.lastTargetX, mob.lastTargetY = nil, nil
        end
        return true
    end,

    notice = function (mob, game, scene)
        if mob.target or not mob.visible then return false end
        local dist = math.max(
            math.abs(mob.x - game.player.x),
            math.abs(mob.y - game.player.y))
        if RNG:random(100) > 100 / (game.player.sneaking + dist) then
            return false
        end
        mob.target = game.player
        game:log(mob.definite, ' notices you!')
        return true
    end,

    distract = function (mob, game, scene)
        if not (mob.target and mob.attention and mob.distraction) then return false end
        if mob.visible then
            mob.lastTargetX = mob.target.x
            mob.lastTargetY = mob.target.y
            mob.distraction = 0
        else
            mob.distraction = mob.distraction + 1
            if mob.distraction >= mob.attention then
                mob.distraction = 0
                mob.target = nil
            end
        end
    end,

    affectPassive = function (mob, game, scene)
        if not mob.effects then return end
        for i = #mob.effects, 1, -1 do
            local effect = mob.effects[i]
            effect.duration = effect.duration - 1
            if effect.duration < 1 then
                table.remove(mob.effects, i)
                effect.revoke(mob, game)
            elseif effect.passive then
                effect.passive(mob, game)
            end
        end
    end,

    affectActive = function (mob, game, scene)
        if not mob.effects then return end
        for i = #mob.effects, 1, -1 do
            local effect = mob.effects[i]
            if effect.active then
                local result = effect.active(mob, game)
                if result then return result end
            end
        end
    end,

    playerCommand = function (mob, game, scene)
        if not mob.command then return end
        local command = game.playState.command[mob.command]
        if not command then return end
        command(game.playState)
        mob.command = nil
    end,

    wait = function (mob, game, scene)
        if not mob.waitTime then return end

        if #scene.visibleMobs > 0 or mob.waitTime < 1
        or mob.health == mob.maxHealth and mob.stamina == mob.maxStamina
        then
            mob.waitTime = nil
            scene:computeFov()
            game:refresh()
            return false
        else
            mob.waitTime = mob.waitTime - 1
            mob.delay = mob.delay + 5
            scene:computeFov()
            if mob.waitTime % 20 == 0 then game:refresh() end
            return true
        end
    end,

}
